/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d;

import org.eclipse.swt.graphics.Color;

import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;

/**
 * SchemeBorder allows the creation of borders based on
 * {@link SchemeBorder.Scheme Schemes}. A <i>Scheme</i> is a class whose only purpose is
 * to carry border specific information. SchemeBorder renders the border based on the 
 * information given by the <i>Scheme</i> set to it.
 */
public class SchemeBorder
	extends AbstractBorder
	implements ColorConstants
{

/** The {@link SchemeBorder.Scheme} associated with this SchemeBorder **/
protected Scheme scheme = null;

/** Arrays of Colors, used for shadow or highlight effects **/
protected static final Color
	DARKEST_DARKER[]   = new Color[] {buttonDarkest,  buttonDarker},
	LIGHTER_DARKER[]   = new Color[] {buttonLightest, buttonDarker},
	DARKER_LIGHTER[]   = new Color[] {buttonDarker, buttonLightest};

/**
 * Holds a set of information about a border, which can be changed to create a wide range 
 * of schemes. Provides support for border opacity, size, highlight side and shadow side 
 * colors.
 */
public static class Scheme {
	private Insets insets;
	private boolean isOpaque = false;
	
	/** Arrays of Colors, used for highlight and shadow effecsts */
	protected Color
		highlight[],
		shadow[];

	/**
	 * Constructs a default border Scheme with no border sides.
	 * @since 2.0
	 */
	protected Scheme() { }

	/**
	 * Constructs a border Scheme with the specified highlight and shadow colors. The size
	 * of the border depends on the number of colors passed in for each parameter. 
	 * Hightlight colors are used in the top and left sides of the border, and Shadow 
	 * colors are used in the bottom and right sides of the border.
	 * 
	 * @param highlight the hightlight colors
	 * @param shadow the shadow colors
	 * @since 2.0
	 */
	public Scheme(Color[] highlight, Color[] shadow) {
		this.highlight = highlight;
		this.shadow = shadow;
		init();
	}

	/**
	 * Constructs a border scheme with the specified colors. The input colors serve as 
	 * both highlight and shadow colors. The size of the border is the number of colors 
	 * passed in as input. Hightlight colors are used in the top and left sides of the 
	 * border, and Shadow colors are used in the bottom and right sides of the border.
	 *
	 * @param colors the colors to be used for the border
	 * @since 2.0
	 */
	public Scheme(Color[] colors) {
		highlight = shadow = colors;
		init();
	}

	/**
	 * Calculates and returns the Insets for this border Scheme. The calculations depend 
	 * on the number of colors passed in as input. 
	 *
	 * @return the Insets used by this border
	 * @since 2.0
	 */
	protected Insets calculateInsets() {
		int tl = getHighlight().length;
		int br = getShadow().length;
		return new Insets(tl, tl, br, br);
	}

	/**
	 * Calculates and retuns the opaque state of this border scheme. Returns 
	 * <code>false</code> if any of the border colors are <code>null</code>. This is done 
	 * to prevent the appearance of underlying pixels since the border color is 
	 * <code>null</code>.
	 * 
	 * @return <code>true</code> if this border is opaque
	 * @since 2.0
	 */
	protected boolean calculateOpaque() {
		Color colors [] = getHighlight();
		for (int i = 0; i < colors.length; i++)
			if (colors[i] == null)
				return false;
		colors = getShadow();
		for (int i = 0; i < colors.length; i++)
			if (colors[i] == null)
				return false;
		return true;
	}
	
	/**
	 * Returns the highlight colors of this border scheme as an array of Colors.
	 *
	 * @return the highlight colors
	 * @since 2.0
	 */
	protected Color[] getHighlight() {
		return highlight;
	}

	/**
	 * Returns the Insets required by this Scheme.
	 *
	 * @return the Insets
	 * @since 2.0
	 */
	protected Insets getInsets() {
		return insets;
	}
	
	/**
	 * Returns the shadow colors of this border scheme as an array of Colors.
	 *
	 * @return the shadow colors
	 * @since 2.0
	 */
	protected Color[] getShadow() {
		return shadow;
	}

	/**
	 * Calculates and initializes the properties of this border scheme.
	 *
	 * @since 2.0
	 */
	protected void init() {
		insets = calculateInsets();
		isOpaque = calculateOpaque();
	}

	/**
	 * Returns whether this border should be opaque or not.
	 *
	 * @return <code>true</code> if this border is opaque
	 * @since 2.0
	 */
	protected boolean isOpaque() {
		return isOpaque;
	}
}

/**
 * Interface which defines some commonly used schemes for the border. These schemes can be
 * given as input to the {@link SchemeBorder SchemeBorder} to generate appropriate borders.
 */
public static interface SCHEMES {
	
	/** Schemes used for shadow and highlight effects **/
	Scheme
		BUTTON_CONTRAST = new Scheme(
			new Color[]{button, buttonLightest},
			DARKEST_DARKER
		), 
		BUTTON_RAISED = new Scheme(
			new Color[]{buttonLightest},
			DARKEST_DARKER
		),
		BUTTON_PRESSED = new Scheme(
			DARKEST_DARKER,
			new Color[]{buttonLightest}
		),
		RAISED = new Scheme(
			new Color[]{buttonLightest},
			new Color[]{buttonDarkest}
		),
		LOWERED = new Scheme(
			new Color[]{buttonDarkest},
			new Color[]{buttonLightest}
		),
		RIDGED = new Scheme(LIGHTER_DARKER, DARKER_LIGHTER),
		ETCHED = new Scheme(DARKER_LIGHTER, LIGHTER_DARKER);
}

/** 
 * Constructs a default SchemeBorder with no scheme defined.
 * @since 2.0
 */
protected SchemeBorder() { }

/**
 * Constructs a SchemeBorder with the Scheme given as input.
 *
 * @param scheme the Scheme to be used by this border
 * @since 2.0
 */
public SchemeBorder(Scheme scheme) {
	setScheme(scheme);
}

/**
 * @see Border#getInsets(IFigure)
 */
public Insets getInsets(IFigure figure) {
	return getScheme().getInsets();
}

/**
 * Returns the scheme used by this border.
 *
 * @return the Scheme used by this border
 * @since 2.0
 */
protected Scheme getScheme() {
	return scheme;
}

/**
 * Returns the opaque state of this border. Returns <code>true</code> indicating that this
 * will fill in the area enclosed by the border.
 * 
 * @see Border#isOpaque()
 */
public boolean isOpaque() {
	return true;
}

/**
 * Sets the Scheme for this border to the Scheme given as input.
 *
 * @param scheme the Scheme for this border
 * @since 2.0
 */
protected void setScheme(Scheme scheme) {
	this.scheme = scheme;
}

/**
 * @see Border#paint(IFigure, Graphics, Insets)
 */
public void paint(IFigure figure, Graphics g, Insets insets) {
	Color [] tl = scheme.getHighlight();
	Color [] br = scheme.getShadow();

	paint (g, figure, insets, tl, br);
}

/**
 * Paints the border using the information in the set Scheme and the inputs given. Side 
 * widths are determined by the number of colors in the Scheme for each side. 
 * 
 * @param graphics the graphics object
 * @param fig the figure this border belongs to
 * @param insets the insets
 * @param tl the highlight (top/left) colors
 * @param br the shadow (bottom/right) colors
 */
protected void paint(Graphics graphics, IFigure fig, 
						Insets insets, Color[] tl, Color[] br) {
	graphics.setLineWidth(1);
	graphics.setLineStyle(Graphics.LINE_SOLID);
	graphics.setXORMode(false);

	Rectangle rect = getPaintRectangle(fig, insets);

	int top    = rect.y;
	int left   = rect.x;
	int bottom = rect.bottom() - 1;
	int right  = rect.right() - 1;
	Color color;

	for (int i = 0; i < br.length; i++) {
		color = br[i];
		graphics.setForegroundColor(color);
		graphics.drawLine(right - i, bottom - i, right - i, top + i);
		graphics.drawLine(right - i, bottom - i, left + i, bottom - i);
	}

	right--;
	bottom--;

	for (int i = 0; i < tl.length; i++) {
		color = tl[i];
		graphics.setForegroundColor(color);
		graphics.drawLine(left + i, top + i, right - i, top + i);
		graphics.drawLine(left + i, top + i, left + i, bottom - i);
	}
}

}
